import { v4 as uuidv4 } from 'uuid';

class Engine {
  constructor () {
    this.nodeMap = new Map()
  }

  parseAttributes(str) {
    let attrs = new Map()
    str = str.trim()
    str.replace(/([\w|-]+)\s*=['"](.*?)['"]/gm, (s0, s1, s2) => {
      attr.set(s1, s2);
      return s0;
    });
    return attrs
  }


  parseToVNode(template) {
    let re = /\((.*?)\)/g;
    let stack = [];
    let parent = new VNode("root", {}, [], null, template, null);
    stack.push(parent);
    while (stack.length > 0) {
      let parentNode = stack.pop()
      let nodeStr = parentNode.childrenTemplate
      re.lastIndex = 0
      Array.from(nodeStr.matchAll(re)).forEach(item => {
        const n = this.nodeMap.get(item[1])
        const { tag, attrs, childrenTemplate } = n
        const newNode = new VNode(tag, attrs, [], parentNode, childrenTemplate)
        parentNode.children.push(newNode)
        stack.push(newNode)
      })
    }
    return parent.children[0]
  }

  parseNodeToDom(root, data) {
    let fragment = document.createDocumentFragment();
    let stack = [[root, fragment, data]];
    //转成成node节点
    while (stack.length > 0) {
      let [parentNode, parentDom, scope] = stack.pop();
      if (parentNode.attrs.get("v-if")) {
        let [key, prop] = parentNode.attrs.get("v-if").split(".");
        key = key.trim();
        prop = prop.trim();
        const isRender = scope[key][prop];
        if (!isRender) {
          continue;
        }
      }

      const html = this.parseHtml(parentNode, data, scope);
      const element = this.createElement(parentNode, html);
      this.updateAttributes(element, parentNode, data, scope);
      parentDom.appendChild(element);

      for (let i = 0; i < parentNode.children?.length; i++) {
        stack.push([parentNode.children[i], element, scope]);
      }
    }
    return fragment;
  }


  parseHtml(node, globalScope, currentScope) {
    return node.childrenTemplate.replace(/{{(.*?)}}/g, (s0, s1) => {
      let props = s1.split(".");
      let val = currentScope[props[0]] || globalScope[props[0]];
      props.slice(1).forEach((item) => {
        val = val[item];
      });
      return val;
    });
  }

  updateAttributes(ele, node, globalScope, currentScope) {
    const { attrs } = node
    for (let [key, value] of attrs) {
      let result = /{{(.*?)}}/.exec(value);
      if (result && result.length > 0) {
        let props = result[1].split(".");
        let val = currentScope[props[0]] || globalScope[props[0]];
        props.slice(1).forEach((item) => {
          val = val[item];
        });
        ele.setAttribute(key, val);
      }
    }
  }

  createElement(node, html) {
    let ignoreAttr = ["v-if"];
    let dom = document.createElement(node.tag);
    for (let [key, val] of node.attr) {
      if (!ignoreAttr.includes(key)) {
        dom.setAttribute(key, val);
      }
    }
    if (node.children.length === 0) {
      dom.innerHTML = html;
    }
    return dom;
  }

  render = (tmpl, data) => {
    const rootNode = this.setNodes(tmpl);
    return this.parseNodeToDom(rootNode, data);
  }

  setNodes = (tmpl) => {
    const re1 = /<(\w+)\s*([^>]*)>([^<]*)<\/\1>/gm;
    const re2 = /<(\w+)\s*([^(/>)]*)\/>/gm;

    let template = tmpl.replace(/\n/gm, "");
    while (re1.test(template) || re2.test(template)) {
      template = template.replace(re1, (s0, s1, s2, s3) => {
        let attr = this.parseAttribute(s2);
        let node = new VNode(s1, attr, [], null, s3);
        this.nodes.set(node.uuid, node);
        return `(${node.uuid})`;
      });

      template = template.replace(re2, (s0, s1, s2) => {
        let attr = this.parseAttribute(s2);
        let node = new VNode(s1, attr, [], null, "");
        this.nodes.set(node.uuid, node);
        return `(${node.uuid})`;
      });
    }
    return this.parseToNode(template);
  };
}


class VNode {
  constructor(tag, attrs, children, parent, childrenTemplate) {
    this.tag = tag;
    this.attrs = attrs;
    this.children = children;
    this.parent = parent;
    this.childrenTemplate = childrenTemplate.trim();
    this.uuid = uuidv4()
  }
}